#!/usr/bin/env python
# coding=utf-8
"""
    secchecksuite/seccheck_log
    seccheck_log for log management and  out report
    :CopyRight(c) 2019 Inspur Electronic Information Industry Co.,Ltd
"""
# example
# import seccheck_log
# seccheck_log.sec_check_logger.info('sffssaf')
import sys
from logging import Formatter
from copy import copy
import logging
from logging.handlers import RotatingFileHandler
import os
import datetime
import time
import threading
import constant as const
import utils

# global var
color_dict = {'PASS': 32, 'INFO': 33, 'WARN': 31}

PREFIX = '\033['
SUFFIX = '\033[0m'
log_level_dict = {1: "INFO", 2: "WARN", 3: "ERROR", 4: "CRITICAL"}
report_logger = None
sec_check_logger = None
tmp_logger = None


def file_rename(logfile):
    """
    rename file:logfile backup file will be logfile.0, logfile.1, logfile.2
    if three file is all exists, remove file that is earliest
    Args:
        logfile:logfile name
    Returns:
        None
    Raises:
        json.decoder.JSONDecodeError:
        IOError: main config file not exist
    """
    try:
        if os.path.exists(logfile):
            flag = 0
            last_file_name = ""
            modify_time = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
            for i in range(0, 3):
                new_file = '%s.%s' % (logfile, i)
                if os.path.exists(new_file):
                    filetime = get_file_modification_time(new_file)
                    if filetime < modify_time:
                        modify_time = filetime
                        last_file_name = new_file
                    continue
                else:
                    last_file_name = new_file
                    flag = 1
                    break
            if flag == 0:
                os.remove(last_file_name)
            os.rename(logfile, last_file_name)
    except BaseException as e:
        print('rename log file error:{}'.format(e))


def get_file_modification_time(filename):
    """
    get file modification time
    Args:
        filename: filename
    Returns:
        modification time
    """
    t = os.path.getmtime(filename)
    format_time = time.strftime("%Y%m%d%H%M%S", time.localtime(t))
    return format_time


_lock = threading.RLock()


def _acquire_lock():
    """
    Acquire the module-level lock for serializing access to shared data.
    This should be released with _releaseLock().
    Args:
    Returns:
    """
    if _lock:
        _lock.acquire()


def _release_lock():
    """
    Release the module-level lock acquired by calling _acquireLock().
    Args:
    Returns:
    """
    if _lock:
        _lock.release()


class ConsoleColorFormatter(Formatter):
    """
    ConsoleColor  inherit from class Formatter for print color in cosole
    Attributes:
        patern: print content
    """

    def __init__(self, patern):
        Formatter.__init__(self, patern)

    def format(self, record):
        color_record = copy(record)
        msg = '%s' % color_record.msg
        if msg.find('PASS') == -1 and msg.find('WARN') == -1 and msg.find('INFO') == -1:
            pass
        else:
            msg = color_record.msg.split(' ', 1)
            seq = color_dict.get(msg[0], 37)
            message = '{0}{1}m[{2}]{3} {4}'.format(PREFIX, seq, msg[0], SUFFIX, msg[1])
            color_record.msg = message
        return Formatter.format(self, color_record)


class AdapterLogger(logging.Logger):
    """
    AdapterLogger inherit from logging.logger ,in order to suite for plugin
    Attributes:
        name: logger object name
    """

    def __init__(self, name):
        super(AdapterLogger, self).__init__(name)

    def debug_debug(self, user, module, task, *args, **kwargs):
        list_args = [item for item in args]
        dict_args = {key: value for (key, value) in kwargs.items()}
        params = '{0}-{1}-{2}-{3}-{4}'.format(user, module, task, list_args, dict_args)
        self.info(params)

    def debug_info(self, user, module, task, *args, **kwargs):
        list_args = [item for item in args]
        dict_args = {key: value for (key, value) in kwargs.items()}
        params = '{0}-{1}-{2}-{3}-{4}'.format(user, module, task, list_args, dict_args)
        self.info(params)

    def debug_warning(self, user, module, task, *args, **kwargs):
        list_args = [item for item in args]
        dict_args = {key: value for (key, value) in kwargs.items()}
        params = '{0}-{1}-{2}-{3}-{4}'.format(user, module, task, list_args, dict_args)
        self.warning(params)

    def debug_error(self, user, module, task, *args, **kwargs):
        list_args = [item for item in args]
        dict_args = {key: value for (key, value) in kwargs.items()}
        params = '{0}-{1}-{2}-{3}-{4}'.format(user, module, task, list_args, dict_args)
        self.error(params)


def get_logger(name=None, adapter_logger=True):
    """
    get logger object
    Args:
        name: logger object name
        adapter_logger: is /not adapter
    Returns:
        logger:logger object
    Raises:
        None
    """
    if not adapter_logger:
        return logging.getLogger(name)
    logging_class = logging.getLoggerClass()  # store the current logger factory for later
    # logging._acquireLock()  # use the global logging lock for thread safety
    _acquire_lock()
    try:
        logging.setLoggerClass(AdapterLogger)  # temporarily change the logger factory
        log_ger = logging.getLogger(name)
        logging.setLoggerClass(logging_class)  # be nice, revert the logger factory change
        return log_ger
    finally:
        # logging._releaseLock()
        _release_lock()


def init_intermediate_file(log_dict):
    """
     init intermediate file
    Args:
        log_dict: log config
    Returns:
        logger tmp logger
    Raises:
        None
    """
    global tmp_logger
    log_path = log_dict.get('logpath', const.LOGPATH)
    try:
        if not os.path.exists(log_path):
            os.makedirs(log_path)
        # 临时文件logger
        tmp_log_file = os.path.join(log_path, const.RESULTFILE + '.json')
        # 重命名中间结果临时文件
        file_rename(tmp_log_file)
        tmp_logger = setup_result_logger(const.RESULTFILE, tmp_log_file)
        return tmp_logger
    except BaseException as e:
        sec_check_logger.error('init_log occur error:{} quit.'.format(e))
        utils.program_exit(sys.argv[0], -1)


def init_logger(config_dict):
    """
    Setup logging configuration
    Args:
        config_dict: log config
    Returns:
        tmp_log_file：tmp log file path
    Raises:
        json.decoder.JSONDecodeError:
        TypeError:
        OverflowError:
    """
    global sec_check_logger
    global report_logger
    file_suffix = config_dict.get('exporttype', 'json')
    log_info_dict = config_dict.get('loginfo', None)
    if log_info_dict:
        log_path = log_info_dict.get('logpath', const.LOGPATH)
        console_log_level = log_info_dict.get('console_log_level', 3)
        if not isinstance(console_log_level, int):
            raise TypeError('console_log_level not int')
        log_level = log_info_dict.get('log_level', 1)
        if not isinstance(log_level, int):
            raise TypeError('log_level not int')
        log_max_size = log_info_dict.get('logmaxsize', 20)
        if not isinstance(log_max_size, int):
            raise TypeError('log_max_size need int')
        if log_max_size <= 0 or log_max_size > 100:
            raise OverflowError('logmaxsize not in (0 - 100]')
    else:
        log_path = const.LOGPATH
        console_log_level = 3
        log_level = 1
        log_max_size = 20
    if len(log_path.strip()) == 0:
        raise IOError('log_path is invalid')
    try:
        if not os.path.exists(log_path):
            os.makedirs(log_path)
    except OSError as e:
        raise e
    if not os.access(log_path, os.R_OK | os.W_OK):
        des = 'log path:{} Permission denied'.format(log_path)
        raise OSError(des)
    try:
        # 临时文件路径
        tmp_log_file = os.path.join(log_path, const.RESULTFILE + '.json')

        # 扫描报告logger
        report_file = const.REPORTFILE + '.' + file_suffix.lower()
        log_file = os.path.join(log_path, report_file)
        # 重命名扫描报告logger
        file_rename(log_file)
        report_logger = setup_result_logger(const.REPORTFILE, log_file)

        # 日志logger
        log_file = os.path.join(log_path, const.LOGNAME)
        sec_check_logger = setup_sec_check_logger(log_file, log_max_size, log_level_dict[console_log_level],
                                                  log_level_dict[log_level])

        return tmp_log_file
    except BaseException as e:
        print("log init failed:{}".format(e))
        utils.program_exit(sys.argv[0], -1)


def setup_result_logger(name, log_file, log_level='ERROR'):
    """
    create log object
    Args:
        name: logger name
        log_file:log file name
        log_level: log level
    Returns:
        return a logger
    Raises:
        none
    """

    formatter = logging.Formatter('%(message)s')

    handler = logging.FileHandler(log_file)
    handler.setFormatter(formatter)

    result_logger = get_logger(name)
    result_logger.setLevel(log_level)
    result_logger.addHandler(handler)
    return result_logger


def setup_sec_check_logger(logfile, log_max_size=10, console_log_level='INFO', log_level='INFO', backup=5):
    """create log object
        Args:
            logfile:log file name
            log_max_size: log max bytes
            console_log_level:
            log_level: log level
            backup: back file numbers
        Returns:
            return a logger
        Raises:
            none
    """
    seccheck_logger = get_logger('seccheck')
    # Add console handler using our custom ColoredFormatter
    ch = logging.StreamHandler()
    ch.setLevel(console_log_level)
    cf = ConsoleColorFormatter("%(message)s")
    ch.setFormatter(cf)
    seccheck_logger.addHandler(ch)

    # Add file handler
    fh = RotatingFileHandler(logfile, maxBytes=log_max_size * 1000 * 1000, backupCount=backup)
    fh.setLevel(log_level)
    ff = logging.Formatter('%(asctime)s - %(module)s - %(lineno)d - %(levelname)s - %(message)s')
    fh.setFormatter(ff)
    seccheck_logger.addHandler(fh)

    # Set log level
    seccheck_logger.setLevel(logging.INFO)
    return seccheck_logger
